use std::cell::RefCell;
use std::collections::HashMap;
use std::ops::Deref;
use std::rc::Rc;
use crate::get_constant_info_from_vm_class;
use crate::instruction::instruction::Instruction;
use crate::class::vm_access_flags::is_static;
use crate::class::vm_class::VMClass;
use crate::class::vm_class_constant_info::VMClassConstantInfoEnum;
use crate::class::vm_class_constant_info::VMClassConstantInfoNameAndType;
use crate::vm_thread::{VMFrame, VMThread};

pub struct invokespecial {
    pub index: u16,
}

impl Instruction for invokespecial {
    fn read_operands(&mut self, frame: Rc<RefCell<VMFrame>>) {
        self.index = frame.borrow_mut().read_u16();
    }

    fn exec(&mut self, frame: Rc<RefCell<VMFrame>>, thread: &VMThread) {
        let mut method_ref = {
            let ro_frame = frame.borrow();
            get_constant_info_from_vm_class!(ro_frame.current_vm_class, self.index, VMClassConstantInfoMethodRef)
        };

        method_ref.init(thread);

        let method = method_ref.vm_class_method.clone().unwrap();
        let current_vm_class = method_ref.vm_class.clone().unwrap();
        if !current_vm_class.has_init() {
            VMClass::init(current_vm_class, frame, thread);
            return;
        }
        let code_attr = {
            let result = method.get_code_attr();
            if result.is_err() {
                panic!("{},{},{}", file!(), line!(), result.err().unwrap());
            }
            result.unwrap()
        };
        let stack_size = code_attr.max_stack;
        let vars_size = code_attr.max_locals;

        let new_frame = Rc::new(RefCell::new(VMFrame {
            pc: 0,
            code: code_attr.code.clone(),
            method,
            current_vm_class,
            operand_stack: RefCell::new(Vec::with_capacity(stack_size as usize)),
            local_vars: RefCell::new(HashMap::with_capacity(vars_size as usize)),
        }));

        {
            let ro_frame = frame.borrow();
            let ro_new_frame = new_frame.borrow();
            let mut rw_local_vars = ro_new_frame.local_vars.borrow_mut();
            let mut rw_stack = ro_frame.operand_stack.borrow_mut();
            let mut i = code_attr.args_count as i32 - 1;
            while i >= 0 {
                let val = rw_stack.pop();
                rw_local_vars.insert(i as u8, val.unwrap());
                i -= 1;
            }
        }
        thread.stack.borrow_mut().push(new_frame);
    }

    fn to_string(&self) -> String {
        String::from("invokespecial")
    }
}